﻿using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom;
using DesignPatternToolkit.Core;

namespace DesignPatternToolkit.Patterns.Singleton
{
    public class SingletonBuilder : PatternBuilder
    {
        public SingletonBuilder()
        {
            Configuration = new SingletonConfiguration();
        }

        public override string PatternName
        {
            get
            {
                return "Singleton";
            }
        }

        public SingletonConfiguration SingletonConfiguration
        {
            get
            {
                return Configuration as SingletonConfiguration;
            }
        }

        protected override List<CodeNamespace> CreateClassesCore()
        {
            /* First, create the namespace. */
            CodeNamespace nameSpace = new CodeNamespace(Configuration.Namespace);

            CodeTypeDeclaration singletonClass = new CodeTypeDeclaration();
            singletonClass.Name = SingletonConfiguration.SingletonClassName;

            CodeTypeReference codeTypeRef = new CodeTypeReference(singletonClass.Name);

            /* Create a private static field. 
             * private static <classType> _instance; */
            CodeMemberField singletonField = new CodeMemberField(codeTypeRef, "_instance");
            singletonField.Attributes = MemberAttributes.Private | MemberAttributes.Static;
            singletonClass.Members.Add(singletonField);

            CodeFieldReferenceExpression fieldRef = new CodeFieldReferenceExpression();
            fieldRef.FieldName = singletonField.Name;

            /* Create a protected constructor. 
             * protected <classType>()
             * { }
             */
            CodeConstructor singletonConstructor = new CodeConstructor();
            singletonConstructor.Attributes = MemberAttributes.Family;
            singletonClass.Members.Add(singletonConstructor);

            /* Create the static instance property. 
             * public static <classType> Instance     ==> CodeMemberProperty()
             * {
             *      get 
             *      { 
             *          if (_instance == null) ==> CodeConditionStatement
             *          {
             *              _instance = new <classType>(); ==> CodeAssignStatement icm CodeObjectCreateExpression
             *          }
             *          return _instance;   ==> CodeMethodReturnStatement()
             *      }
             * }
             */
            CodeMemberProperty instanceProperty = new CodeMemberProperty();
            instanceProperty.Name = "Instance";
            instanceProperty.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            instanceProperty.HasGet = true;
            instanceProperty.HasSet = false;
            instanceProperty.Type = codeTypeRef;
            singletonClass.Members.Add(instanceProperty);

            /* Build the if statement: (_instance == null) */
            CodeConditionStatement ifStatement = new CodeConditionStatement();
            ifStatement.Condition = new CodeSnippetExpression("_instance == null");
            
            /* Build the true part: _instance = new <classType>(); 
             * Could be solved using a CodeObjectCreateExpression. */
            CodeObjectCreateExpression createInstance = new CodeObjectCreateExpression(codeTypeRef, new CodeExpression[] { });
            CodeAssignStatement assignToInstance = new CodeAssignStatement(fieldRef, createInstance);
            ifStatement.TrueStatements.Add(assignToInstance);

            /* Build the return statement. */
            CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement();
            returnStatement.Expression = fieldRef ;

            /* Put everything together before adding it to the getter */
            instanceProperty.GetStatements.Add(ifStatement);
            instanceProperty.GetStatements.Add(returnStatement);

            nameSpace.Types.Add(singletonClass);

            List<CodeNamespace> result = new List<CodeNamespace>();
            result.Add(nameSpace);

            return result;
        }
    }
}
